home *** CD-ROM | disk | FTP | other *** search
/ Mac Easy 2010 May / Mac Life Ubuntu.iso / casper / filesystem.squashfs / usr / share / pyshared / gst-0.10 / gst / extend / leveller.py < prev    next >
Encoding:
Python Source  |  2009-02-21  |  9.7 KB  |  286 lines

  1. # -*- Mode: Python -*-
  2. # vi:si:et:sw=4:sts=4:ts=4
  3. #
  4. # GStreamer python bindings
  5. # Copyright (C) 2005 Thomas Vander Stichele <thomas at apestaart dot org>
  6.  
  7. # This library is free software; you can redistribute it and/or
  8. # modify it under the terms of the GNU Lesser General Public
  9. # License as published by the Free Software Foundation; either
  10. # version 2.1 of the License, or (at your option) any later version.
  11. # This library is distributed in the hope that it will be useful,
  12. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  14. # Lesser General Public License for more details.
  15. # You should have received a copy of the GNU Lesser General Public
  16. # License along with this library; if not, write to the Free Software
  17. # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
  18.  
  19. import os
  20. import sys
  21. import math
  22.  
  23. import gobject
  24. import pygst
  25. pygst.require('0.10')
  26. import gst
  27.  
  28. import utils
  29. from pygobject import gsignal
  30.  
  31. import sources
  32. from sources import EOS, ERROR, UNKNOWN_TYPE, WRONG_TYPE
  33.  
  34. class Leveller(gst.Pipeline):
  35.     """
  36.     I am a pipeline that calculates RMS values and mix-in/out points.
  37.     I will signal 'done' when I'm done scanning the file, with return value
  38.     EOS, ERROR, UNKNOWN_TYPE, or WRONG_TYPE from gst.extend.sources
  39.     """
  40.  
  41.     gsignal('done', str)
  42.  
  43.     def __init__(self, filename, threshold=-9.0):
  44.         gst.Pipeline.__init__(self)
  45.  
  46.         self._filename = filename
  47.  
  48.         self._thresholddB = threshold
  49.         self._threshold = math.pow(10, self._thresholddB / 10.0)
  50.  
  51.         self._source = sources.AudioSource(filename)
  52.         self._source.connect('done', self._done_cb)
  53.  
  54.         self._level = gst.element_factory_make("level")
  55.  
  56.         self._fakesink = gst.element_factory_make("fakesink")
  57.  
  58.         self.add(self._source, self._level, self._fakesink)
  59.         self._source.connect("pad-added", self._sourcePadAddedCb)
  60.         self._level.link(self._fakesink)
  61.  
  62.         # temporary values for each timepoint
  63.         self._rmsdB = {} # hash of channel, rmsdB value
  64.         self._peakdB = 0.0 # highest value over all channels for this time
  65.  
  66.         # results over the whole file
  67.         self._meansquaresums = [] # list of time -> mean square sum value
  68.         self._peaksdB = [] # list of time -> peak value
  69.  
  70.         self._lasttime = 0
  71.  
  72.         # will be set when done
  73.         self.mixin = 0
  74.         self.mixout = 0
  75.         self.length = 0
  76.         self.rms = 0.0
  77.         self.rmsdB = 0.0
  78.  
  79.     def _sourcePadAddedCb(self, source, pad):
  80.         self._source.link(self._level)
  81.  
  82.     def do_handle_message(self, message):
  83.         self.debug("got message %r" % message)
  84.         if (message.type == gst.MESSAGE_ELEMENT) and (message.src == self._level):
  85.             struc = message.structure
  86.             endtime = struc["endtime"]
  87.             rmss = struc["rms"]
  88.             peaks = struc["peak"]
  89.             decays = struc["decay"]
  90.             infos = zip(rmss, peaks, decays)
  91.             channid = 0
  92.             for rms,peak,decay in infos:
  93.                 self._level_cb(message.src, endtime, channid, rms, peak, decay)
  94.                 channid += 1
  95.         elif message.type == gst.MESSAGE_EOS:
  96.             self._eos_cb(message.src)
  97.         # chaining up 
  98.         gst.Pipeline.do_handle_message(self, message)
  99.  
  100.     def _level_cb(self, element, time, channel, rmsdB, peakdB, decaydB):
  101.         # rms is being signalled in dB
  102.         # FIXME: maybe level should have a way of signalling actual values
  103.         # signals are received in order, so I should get each channel one
  104.         # by one
  105.         if time > self._lasttime and self._lasttime > 0:
  106.             # we have a new time point, so calculate stuff for the old block
  107.             meansquaresum = 0.0
  108.             for i in self._rmsdB.keys():
  109.                 meansquaresum += math.pow(10, self._rmsdB[i] / 10.0)
  110.             # average over channels
  111.             meansquaresum /= len(self._rmsdB.keys())
  112.             try:
  113.                 rmsdBstr = str(10 * math.log10(meansquaresum))
  114.             except OverflowError:
  115.                 rmsdBstr = "(-inf)"
  116.             gst.log("meansquaresum %f (%s dB)" % (meansquaresum, rmsdBstr))
  117.  
  118.             # update values
  119.             self._peaksdB.append((self._lasttime, peakdB))
  120.             self._meansquaresums.append((self._lasttime, meansquaresum))
  121.             self._rmsdB = {}
  122.             self._peakdB = 0.0
  123.  
  124.         # store the current values for later processing
  125.         gst.log("time %s, channel %d, rmsdB %f" % (gst.TIME_ARGS(time), channel, rmsdB))
  126.         self._lasttime = time
  127.         self._rmsdB[channel] = rmsdB
  128.         if peakdB > self._peakdB:
  129.             self._peakdB = peakdB
  130.  
  131.     def _done_cb(self, source, reason):
  132.         gst.debug("done, reason %s" % reason)
  133.         # we ignore eos because we want the whole pipeline to eos
  134.         if reason == EOS:
  135.             return
  136.         self.emit('done', reason)
  137.  
  138.     def _eos_cb(self, source):
  139.         gst.debug("eos, start calcing")
  140.  
  141.         # get the highest peak RMS for this track
  142.         highestdB = self._peaksdB[0][1]
  143.  
  144.         for (time, peakdB) in self._peaksdB:
  145.             if peakdB > highestdB:
  146.                 highestdB = peakdB
  147.         gst.debug("highest peak(dB): %f" % highestdB)
  148.  
  149.         # get the length
  150.         (self.length, peakdB) = self._peaksdB[-1]
  151.         
  152.         # find the mix in point
  153.         for (time, peakdB) in self._peaksdB:
  154.             gst.log("time %s, peakdB %f" % (gst.TIME_ARGS(time), peakdB))
  155.             if peakdB > self._thresholddB + highestdB:
  156.                 gst.debug("found mix-in point of %f dB at %s" % (
  157.                     peakdB, gst.TIME_ARGS(time)))
  158.                 self.mixin = time
  159.                 break
  160.  
  161.         # reverse and find out point
  162.         self._peaksdB.reverse()
  163.         found = None
  164.         for (time, peakdB) in self._peaksdB:
  165.             if found:
  166.                 self.mixout = time
  167.                 gst.debug("found mix-out point of %f dB right before %s" % (
  168.                     found, gst.TIME_ARGS(time)))
  169.                 break
  170.                 
  171.             if peakdB > self._thresholddB + highestdB:
  172.                 found = peakdB
  173.  
  174.         # now calculate RMS between these two points
  175.         weightedsquaresums = 0.0
  176.         lasttime = self.mixin
  177.         for (time, meansquaresum) in self._meansquaresums:
  178.             if time <= self.mixin:
  179.                 continue
  180.  
  181.             delta = time - lasttime
  182.             weightedsquaresums += meansquaresum * delta
  183.             gst.log("added MSS %f over time %s at time %s, now %f" % (
  184.                 meansquaresum, gst.TIME_ARGS(delta),
  185.                 gst.TIME_ARGS(time), weightedsquaresums))
  186.  
  187.             lasttime = time
  188.             
  189.             if time > self.mixout:
  190.                 break
  191.  
  192.         # calculate
  193.         try:
  194.             ms = weightedsquaresums / (self.mixout - self.mixin)
  195.         except ZeroDivisionError:
  196.             # this is possible when, for example, the whole sound file is
  197.             # empty
  198.             gst.warning('ZeroDivisionError on %s, mixin %s, mixout %s' % (
  199.                 self._filename, gst.TIME_ARGS(self.mixin),
  200.                 gst.TIME_ARGS(self.mixout)))
  201.             self.emit('done', WRONG_TYPE)
  202.             return
  203.  
  204.         self.rms = math.sqrt(ms)
  205.         self.rmsdB = 10 * math.log10(ms)
  206.  
  207.         self.emit('done', EOS)
  208.  
  209.     def start(self):
  210.         gst.debug("Setting to PLAYING")
  211.         self.set_state(gst.STATE_PLAYING)
  212.         gst.debug("Set to PLAYING")
  213.  
  214.     # FIXME: we might want to do this ourselves automatically ?
  215.     def stop(self):
  216.         """
  217.         Stop the leveller, freeing all resources.
  218.         Call after the leveller emitted 'done' to clean up.
  219.         """
  220.         gst.debug("Setting to NULL")
  221.         self.set_state(gst.STATE_NULL)
  222.         gst.debug("Set to NULL")
  223.         utils.gc_collect('Leveller.stop()')
  224.  
  225.     def clean(self):
  226.         # clean ourselves up completely
  227.         self.stop()
  228.         # let's be ghetto and clean out our bin manually
  229.         self.remove(self._source)
  230.         self.remove(self._level)
  231.         self.remove(self._fakesink)
  232.         gst.debug("Emptied myself")
  233.         self._source.clean()
  234.         utils.gc_collect('Leveller.clean() cleaned up source')
  235.         self._source = None
  236.         self._fakesink = None
  237.         self._level = None
  238.         utils.gc_collect('Leveller.clean() done')
  239.  
  240. gobject.type_register(Leveller)
  241.  
  242. if __name__ == "__main__":
  243.     main = gobject.MainLoop()
  244.  
  245.     try:
  246.         leveller = Leveller(sys.argv[1])
  247.     except IndexError:
  248.         sys.stderr.write("Please give a file to calculate level of\n")
  249.         sys.exit(1)
  250.  
  251.     print "Starting"
  252.     bus = leveller.get_bus()
  253.     bus.add_signal_watch()
  254.     dontstop = True
  255.  
  256.     leveller.set_state(gst.STATE_PLAYING)
  257.     
  258.     while dontstop:
  259.         message = bus.poll(gst.MESSAGE_ANY, gst.SECOND)
  260.         if message:
  261.             gst.debug("got message from poll:%s/%r" % (message.type, message))
  262.         else:
  263.             gst.debug("got NOTHING from poll")
  264.         if message:
  265.             if message.type == gst.MESSAGE_EOS:
  266.                 print "in: %s, out: %s, length: %s" % (gst.TIME_ARGS(leveller.mixin),
  267.                                                        gst.TIME_ARGS(leveller.mixout),
  268.                                                        gst.TIME_ARGS(leveller.length))
  269.                 print "rms: %f, %f dB" % (leveller.rms, leveller.rmsdB)
  270.                 dontstop = False
  271.             elif message.type == gst.MESSAGE_ERROR:
  272.                 error,debug = message.parse_error()
  273.                 print "ERROR[%s] %s" % (error.domain, error.message)
  274.                 dontstop = False
  275.  
  276.     leveller.stop()
  277.     leveller.clean()
  278.  
  279.     gst.debug('deleting leveller, verify objects are freed')
  280.     utils.gc_collect('quit main loop')
  281.     del leveller
  282.     utils.gc_collect('deleted leveller')
  283.     gst.debug('stopping forever')
  284.